//
//  JsonLoader.cpp
//  roborobo
//
//  Created by Berend Weel on 6/26/13.
//
//

/**
 * Loads the collision fixtures defined with the Physics Body Editor
 * application. You only need to give it a body and the corresponding fixture
 * name, and it will attach these fixtures to your body.
 *
 * @author Aurelien Ribon | http://www.aurelienribon.com
 */
#include <fstream>
#include <streambuf>

#include "JsonLoader.h"
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/exceptions.hpp>
    
// -------------------------------------------------------------------------
// Ctors
// -------------------------------------------------------------------------      
BodyEditorLoader::BodyEditorLoader(std::string str) {
    try {
        model = readJson(str);
    } catch (boost::property_tree::json_parser_error &e) {
        std::cerr << "A parsing error occured: " << e.what() << std::endl;
    }
}

// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------

/**
 * Creates and applies the fixtures defined in the editor. The name
 * parameter is used to retrieve the right fixture from the loaded file.
 * <br/><br/>
 *
 * The body reference point (the red cross in the tool) is by default
 * located at the bottom left corner of the image. This reference point
 * will be put right over the BodyDef position point. Therefore, you should
 * place this reference point carefully to let you place your body in your
 * world easily with its BodyDef.position point. Note that to draw an image
 * at the position of your body, you will need to know this reference point
 * (see {@link #getOrigin(java.lang.String, float)}.
 * <br/><br/>
 *
 * Also, saved shapes are normalized. As shown in the tool, the width of
 * the image is considered to be always 1 meter. Thus, you need to provide
 * a scale factor so the polygons get resized according to your needs (not
 * every body is 1 meter large in your game, I guess).
 *
 * @param body The Box2d body you want to attach the fixture to.
 * @param name The name of the fixture you want to load.
 * @param fd The fixture parameters to apply to the created body fixture.
 * @param scale The desired scale of the body. The default width is 1.
 */
void BodyEditorLoader::attachFixture(b2Body *body, std::string name, b2FixtureDef *fd, float scale) {
    RigidBodyModel *rbModel = model->rigidBodies.at(name);
    if (rbModel == NULL) {
        std::cerr << "Model with name: '" << name << "' was not found." << std::endl;
        return;
    }
    
    b2Vec2 origin(rbModel->origin.x,rbModel->origin.y);
    origin *= scale;
   
    for (int i=0, n=rbModel->polygons.size(); i<n; i++) {
        PolygonModel *polygon = rbModel->polygons.at(i);
        b2Vec2 *vertices = new b2Vec2[polygon->vertices.size()];
        
        for (int ii=0; ii < polygon->vertices.size(); ii++) {
            b2Vec2 vec(polygon->vertices.at(ii).x,polygon->vertices.at(ii).y);
            vec *= scale;
            vec -= origin;
            vertices[ii] = vec;
        }
        
        b2PolygonShape polygonShape;
        polygonShape.Set(vertices,polygon->vertices.size());
        fd->shape = &polygonShape;
        body->CreateFixture(fd);
        
        delete [] vertices;
    }
    
    for (int i=0, n=rbModel->circles.size(); i<n; i++) {
        CircleModel *circle = rbModel->circles.at(i);
        b2Vec2 center(circle->center.x,circle->center.y);
        center *= scale;
        float radius = circle->radius * scale;
        
        b2CircleShape circleShape;
        circleShape.m_p = center;
        circleShape.m_radius = radius;
        fd->shape = &circleShape;
        body->CreateFixture(fd);
    }
}

/**
 * Gets the image path attached to the given name.
 */
std::string BodyEditorLoader::getImagePath(std::string name) {
    RigidBodyModel *rbModel = model->rigidBodies.at(name);
    if (rbModel == NULL){
        std::cerr << "Model with name: '" << name << "' was not found." << std::endl;
        return "";
    }
    
    return rbModel->imagePath;
}

/**
 * Gets the origin point attached to the given name. Since the point is
 * normalized in [0,1] coordinates, it needs to be scaled to your body
 * size. Warning: this method returns the same Vector2 object each time, so
 * copy it if you need it for later use.
 */
b2Vec2 *BodyEditorLoader::getOrigin(std::string name, float scale) {
    RigidBodyModel *rbModel = model->rigidBodies.at(name);
    if (rbModel == NULL){
        std::cerr << "Model with name: '" << name << "' was not found." << std::endl;
    }
    
    b2Vec2 *vec = new b2Vec2(rbModel->origin.x,rbModel->origin.y);
    (*vec) *= scale;
    return vec;
}

/**
 * <b>For advanced users only.</b> Lets you access the internal model of
 * this loader and modify it. Be aware that any modification is permanent
 * and that you should really know what you are doing.
 */
Model *BodyEditorLoader::getInternalModel() {
    return model;
}

// -------------------------------------------------------------------------
// Json reading process
// -------------------------------------------------------------------------

Model *BodyEditorLoader::readJson(std::string str) {
    Model *m = new Model();
    using boost::property_tree::ptree;
    ptree rootElem;
    
    read_json(str,rootElem);
    
    ptree bodiesElems = rootElem.get_child("rigidBodies");
    
    for (ptree::iterator it = bodiesElems.begin(); it!=bodiesElems.end();it++) {
        RigidBodyModel *rbModel = readRigidBody(it->second);
        m->rigidBodies.insert(std::pair<std::string,RigidBodyModel*>(rbModel->name,rbModel));
    }
    
    return m;
}

RigidBodyModel *BodyEditorLoader::readRigidBody(boost::property_tree::ptree bodyElem) {
    using boost::property_tree::ptree;
    RigidBodyModel *rbModel = new RigidBodyModel();
    rbModel->name = bodyElem.get<std::string>("name");
    rbModel->imagePath = bodyElem.get<std::string>("imagePath");
    
    ptree originElem = bodyElem.get_child("origin");
    rbModel->origin.x = originElem.get<float>("x");
    rbModel->origin.y = originElem.get<float>("y");
    
    // polygons  
    ptree polygonsElem = bodyElem.get_child("polygons");
    
    
    for (boost::property_tree::ptree::iterator it = polygonsElem.begin(); it != polygonsElem.end(); it++) {
        PolygonModel *polygon = new PolygonModel();
        rbModel->polygons.push_back(polygon);
        
        ptree verticesElem = it->second;
        for (ptree::iterator tt = verticesElem.begin(); tt != verticesElem.end(); tt++) {
            ptree vertexElem = tt->second;
            float x = vertexElem.get<float>("x");
            float y = vertexElem.get<float>("y");
            b2Vec2 vec(x, y);
            polygon->vertices.push_back(vec);
        }
    }
    // circles
    ptree circlesElem = bodyElem.get_child("circles");
    
    for (ptree::iterator it = circlesElem.begin(); it != circlesElem.end(); it++) {
        CircleModel *circle = new CircleModel();
        rbModel->circles.push_back(circle);
        
        ptree circleElem = it->second;
        circle->center.x = circleElem.get<float>("cx");
        circle->center.y = circleElem.get<float>("cy");
        circle->radius = circleElem.get<float>("r");
    }
    return rbModel;
}